package com.jxm.tree;


import com.jxm.linear.Queue;

/**
 * @Author: jxm
 * @Description: 二叉查找树 API设计
 * @Date: 2022/6/16 14:23
 * @Version: 1.0
 */
public class BinaryTree<Key extends Comparable<Key>,Value> {

    //记录根结点
    private Node root;
    //记录树中元素个数
    private int N;

    private class Node{
        //存储键
        private Key key;
        //存储值
        private Value value;
        //记录左子结点
        private Node left;
        //记录右子结点
        private Node right;

        public Node(Key key,Value value,Node left,Node right){
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }

    public BinaryTree(){}

    //获取树中元素个数
    public int size(){
        return N;
    }

    /**
     * 向树中插入一个键值对
     * @param key
     * @param value
     */
    public void put(Key key,Value value){
        root = put(root,key,value);
    }

    /**
     * 给指定树x上，添加键一个键值对，并返回添加后的新树
     * @param x
     * @param key
     * @param value
     * @return
     */
    private Node put(Node x, Key key, Value value){
        //如果X树为空
        if(x == null){
            N++;
            return new Node(key,value,null,null);
        }
        //如果X树不为空
        //比较X结点的键和key的大小
        int compare = key.compareTo(x.key);
        if(compare > 0){
            //如果key大于X的键，则继续找x结点的右子树
            x.right = put(x.right,key,value);
        }else if(compare < 0){
            //如果key小于X的键，则继续找x结点的左子树
            x.left = put(x.left,key,value);
        }else{
            //如果key等于X的键，则替换X结点的值为value
            x.value = value;
        }

        return x;
    }

    /**
     * 根据key，从树中找出对应的值
     * @param key
     * @return
     */
    public Value get(Key key){
        return get(root,key);
    }

    /**
     * 从指定的树x中，找出key对应的值
     * @param x
     * @param key
     * @return
     */
    private Value get(Node x, Key key){
        //x树为null
        if(x == null){
            return null;
        }
        //x树不为null
        //比较X结点的键和key的大小
        int compare = key.compareTo(x.key);
        if(compare > 0){
            //如果key大于X的键，则继续找x结点的右子树
            return get(x.right,key);
        }else if(compare < 0){
            //如果key小于X的键，则继续找x结点的左子树
            return get(x.left,key);
        }else{
            //如果key等于X的键，就找到键为key的结点，只需要返回x键对应的值即可
            return x.value;
        }

    }

    /**
     * 根据key，删除树中对应的键值对
     * @param key
     */
    public void delete(Key key){
        delete(root,key);
    }

    /**
     * 删除指定树x上的键为key的键值对，并返回删除后的新树
     * @param x
     * @param key
     * @return
     */
    private Node delete(Node x, Key key){
        if(x == null){
            return null;
        }
        //x树不为null
        //比较X结点的键和key的大小
        int compare = key.compareTo(x.key);
        if(compare > 0){
            //如果key大于X的键，则继续找x结点的右子树
            x.right = delete(x.right,key);
        }else if(compare < 0){
            //如果key小于X的键，则继续找x结点的左子树
            x.left = delete(x.left,key);
        }else{
            //如果key等于X结点的键，完成真正的删除结点动作，删除x结点
            //让元素个数-1
            N--;
            //找到右子树最小的结点，如果左右都没有结点，返回左
            if(x.right == null){
                return x.left;
            }
            if(x.left == null){
                return x.right;
            }


            //记录右子结点
            Node minNode = x.right;
            //找到右子树最小的结点
            while (x.left != null){
                minNode = minNode.left;
            }

             //删除树中最小结点
            Node n = x.right;
            while (x.left !=null){
                if(x.left.left == null){
                    n.left = null;
                }else {
                    //变换n的结点
                    n = n.left;
                }
            }

            //让x结点的左子树成为minNode 左子树
            minNode.left = x.left;
            //让x结点的右子树成为minNode 右子树
            minNode.right = x.right;
            //让x结点的父结点指向minNode
            x = minNode;


        }

        return x;
    }

    //查找整个树中最小键
    public Key min(){
        return min(root).key;
    }

    //在指定X树中找出最小键所在的结点
    // 疑问：TODO root 为null报空指针异常
    private Node min(Node x){
        if(x.left!=null){
            return min(x.left);
        }else{
            return x;
        }
    }

    //找出整个树中最大的键
    public Key max(){
        return max(root).key;
    }
    //找出指定树x中最大键所在的结点
    public Node max(Node x){
        if(x.right != null){
            return max(x.right);
        }else {
            return x;
        }
    }

    //获取整个数中所有的键
    public Queue<Key> preErgodic(){
        Queue<Key> keys = new Queue<>();
        preErgodic(root,keys);
        return keys;
    }

    //获取指定数x的所有键，并放到key队列中
    private void preErgodic(Node x, Queue<Key> keys){
        if(root == null){
            return;
        }
        //把x中的节点放入到keys
        keys.enqueue(x.key);
        //递归遍历x结点的左子树
        if(x.left != null){
            preErgodic(x.left,keys);
        }
        //递归遍历x结点的右子树
        if(x.right != null){
            preErgodic(x.right,keys);
        }
    }

    //使用中序遍历，获取整个树中的所有键
    public Queue<Key> midErgodic(){
        Queue<Key> keys = new Queue<>();
        midErgodic(root,keys);
        return keys;
    }

    //使用中序遍历，把指定树x中的所有键放入到keys队列中
    private void midErgodic(Node x, Queue<Key> keys){
        if(x == null){
            return;
        }

        //先递归左子树中的键放入到keys中
        if(x.left != null){
            midErgodic(x.left,keys);
        }

        //把当前结点的键放入到keys中
        keys.enqueue(x.key);
        //在递归，把右子树中的键放入到keys中
        if (x.right !=null){
            midErgodic(x.right,keys);
        }
    }

    //使用后序遍历，获取整个树中的所有键
    public Queue<Key> afterErgodic(){
        Queue<Key> keys = new Queue<>();
        afterErgodic(root,keys);
        return keys;
    }

    //使用后序遍历，把指定树x中的所有键放入到keys队列中
    private void afterErgodic(Node x, Queue<Key> keys){
        if(x == null){
            return;
        }
        //通过递归，把左子树中的键放入到keys中
        if (x.left !=null) {
            afterErgodic(x.left,keys);
        }
        //通过递归，把右子树中的键放入到keys中
        if(x.right !=null){
            afterErgodic(x.right,keys);
        }
        //把当前结点的键放入到keys中
        keys.enqueue(x.key);
    }

    //使用层序遍历，获取整个树中所有的键
    public Queue<Key> layerErgodic(){
        //定义两个队列，分别存储树中的键和树中的结点
        Queue<Key> keys = new Queue<>();
        Queue<Node> nodes = new Queue<>();

        //默认，队列放入根结点
        nodes.enqueue(root);

        while (!nodes.isEmpty()){
            //从队列中弹出一个结点，把key放入到keys中
            Node n = nodes.dequeue();
            keys.enqueue(n.key);
            //判断当前结点还有没有左子结点，则放入nodes中
            if(n.left != null){
                nodes.enqueue(n.left);
            }
            //判断当前结点还有没有右子结点，则放入nodes中
            if(n.right !=null){
                nodes.enqueue(n.right);
            }
        }
        return keys;
    }

    //获取整个树中的最大深度
    public int maxDepth(){
        return maxDepth(root);
    }
    //获取指定x树中的最大深度
    public int maxDepth(Node x){
        if (x == null){
            return 0;
        }
        //x的最大深度
        int max = 0;
        //左子树的最大深度
        int maxL = 0;
        //右子树的最大深度
        int maxR = 0;

        //计算X结点左子树的最大深度
        if(x.left !=null){
            maxL = maxDepth(x.left);
        }
        //计算X结点右子树的最大深度
        if(x.right !=null){
            maxR = maxDepth(x.right);
        }
        //比较左子树和右子树的最大深度，取最大值+1
        max = maxL>maxR?maxL+1:maxR+1;

        return max;
    }
}
